#include "StdAfx.h"
#include "LitScriptEngine.h"
#include <LitString.h>
#include <OgrePlatform.h>

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX
	#include <sys/time.h>
	#include <stdio.h>
	#include <termios.h>
	#include <unistd.h>
	#define UINT unsigned int 
	typedef unsigned int DWORD;
	// Linux doesn't have timeGetTime(), this essintially does the same
	// thing, except this is milliseconds since Epoch (Jan 1st 1970) instead
	// of system start. It will work the same though...
	DWORD timeGetTime()
	{
		timeval time;
		gettimeofday(&time, NULL);
		return time.tv_sec*1000 + time.tv_usec/1000;
	}
#else 
	#include <windows.h> // timeGetTime()
	DWORD timeGetTime()
	{
		return GetTickCount();
	}
#endif

namespace Lit
{
	
	RefPtr<Ogre::Log> ScriptEngine::mRegLog;
	/*/////////////////////////////////////
				SCRIPT ENGINE
	/////////////////////////////////////*/
	

	ScriptEngine::ScriptEngine(Ogre::SceneManager * mgr, Ogre::Log * log )
		: mTimeOut( 1000 )
	{
		mApi = new NativeScriptAPI(this);
		initialise( mgr, log );
	}
	
	ScriptEngine::ScriptEngine(const ScriptEngine& rhs)
		: mEngine( rhs.mEngine )
		, mApi( new NativeScriptAPI(this) )
		, mTimeOut(rhs.mTimeOut)
		, mLog( rhs.mLog )
	{
	}

	ScriptEngine::~ScriptEngine(void)
	{
		if( mEngine )
		{
			destroy();
			mEngine->Release();
			mEngine = Ptr();
		}
		mApi.destroy();
	}
	
	bool ScriptEngine::checkDef( int ret, const std::string &declaration )
	{
		if( ret>=0 ) return true;
		std::string str="[Script Error] Function declaration is invalid in '"+declaration+"' returned: "+trReturnCode(ret);
		logMessage( str );
		MessageBoxA( 0, str.c_str(), "Script Error", MB_ICONWARNING|MB_OK );
		return false;
	}
	void ScriptEngine::setMessageCallback( ){ 
		if( !mEngine ) return;
		mEngine->ClearMessageCallback();
		assert( mEngine->SetMessageCallback( asFUNCTION(MessageCallback), 0, asCALL_CDECL) >= 0 ); 
	}
	void ScriptEngine::setMessageCallback( const asSFuncPtr& func, void * obj, asDWORD callConvention ){ 
		if(!mEngine) return; 
		mEngine->ClearMessageCallback();
		assert( mEngine->SetMessageCallback( func, obj, callConvention) >= 0 ); 
	}
	
	NativeScriptAPICoreFuncsPtr& ScriptEngine::getCore(){ return mApi->getCore(); }
	const NativeScriptAPICoreFuncsPtr& ScriptEngine::getCore() const{ return mApi->getCore(); }
	
	NativeScriptAPIPtr& ScriptEngine::getApi(){ return mApi; }
	const NativeScriptAPIPtr& ScriptEngine::getApi() const{ return mApi; }
	
	void ScriptEngine::initialise( Ogre::SceneManager * mgr, Ogre::Log * log )
	{
		assert( !mEngine );
		mEngine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
		mLog = log?log:Ogre::LogManager::getSingleton().getDefaultLog();
		
		mRegLog = mLog;
		setMessageCallback();
		
		RegisterScriptAny(*mEngine);
		RegisterScriptArray(*mEngine, true);
		RegisterStdString( *mEngine );
		RegisterStdStringUtils( *mEngine );

		mApi->bind( mgr );
	}
	void ScriptEngine::destroy(){
		if( !mEngine ) return;
		for( auto i=mContexts.begin(); i!=mContexts.end(); i++ )
		{
			(*i)->release();
			(*i).destroy();
		}
		mContexts.clear();
		for( auto i=mModules.begin(); i!=mModules.end(); i++ )
		{
			mEngine->DiscardModule( i->get()->getName().c_str() );
			(*i).destroy();
		}
		mModules.clear();
		Ogre::LogManager::getSingleton().destroyLog( *mLog );
	}
	
	void ScriptEngine::LineCallback(asIScriptContext *ctx, DWORD *timeOut)
	{
		// If the time out is reached we abort the script
		if( *timeOut < timeGetTime() )
			ctx->Suspend();

		// It would also be possible to only suspend the script,
		// instead of aborting it. That would allow the application
		// to resume the execution where it left of at a later 
		// time, by simply calling Execute() again.
	}
	void ScriptEngine::MessageCallback(const asSMessageInfo *msg, void *param)
	{
		std::string type;
		bool mb = false;
		if( msg->type == asMSGTYPE_WARNING ) type="WARNING";
		else if( msg->type == asMSGTYPE_INFORMATION ) type="INFO";
		else{ type = "ERROR"; mb = true; }
		std::string str = String(msg->section)(" (")(msg->row)(", ")(msg->col)(") : ")(type)(" : ")(msg->message).str();
		ScriptEngine::getLog()->logMessage(str);
		if( mb ) MessageBoxA( 0, str.c_str(), "[Script Error]", MB_ICONWARNING );
	}

	void ScriptEngine::logMessage( const std::string &msg, Ogre::LogMessageLevel lml, bool maskDebug ){ getLog()->logMessage( msg, lml, maskDebug ); }
	RefPtr<Ogre::Log> ScriptEngine::getLog()
	{ 
		return mRegLog;

	}

	const ScriptEngine::Ptr& ScriptEngine::data() const{ return mEngine; }
	ScriptEngine::Ptr& ScriptEngine::data(){ return mEngine; }
		
	bool ScriptEngine::registerGlobalFunction( const std::string &func_declaration, const asSFuncPtr & func ){
		if (mEngine->RegisterGlobalFunction( func_declaration.c_str(), func, asCALL_CDECL)<0)
        {
			logMessage( "Can't register free function " + func_declaration, Ogre::LML_CRITICAL );
            return false;
        }
		return true;
	}
    bool ScriptEngine::registerClassMem(const std::string& class_name, const std::string& func_declaration, const asSFuncPtr &funcPointer)
	{
		if (mEngine->RegisterObjectMethod(class_name.c_str(), func_declaration.c_str(), funcPointer, asCALL_THISCALL)<0)
        {
			logMessage( "Can't register class member " + func_declaration, Ogre::LML_CRITICAL );
            return false;
        }
		return true;
	}
	ScriptContextPtr ScriptEngine::createContext()
	{
		assert( mEngine );
		return nextFreeContext();
	}
	void ScriptEngine::releaseContext(ScriptContextPtr ptr, bool destroy)
	{
		assert( mEngine );
		if( !ptr ) return;
		if( destroy )
		{
			for( auto i=mContexts.begin(); i!=mContexts.end(); i ++ )
			{
				if( (*i) == ptr )
				{
					mContexts.erase( i );
					break;
				}
			}
			logMessage( "Context destroyed !", Ogre::LML_NORMAL, true );
		}
		ptr->release();
		if( destroy ){ ptr.destroy(); }
		else 
		{	
			logMessage( "Context released !", Ogre::LML_NORMAL, true );
		}
	}
		
	ScriptContextState ScriptEngine::runTimeOut( ScriptContextPtr ctx, DWORD timeOut ){
		DWORD to = timeGetTime()+timeOut;
		ctx->data()->ClearLineCallback();
		ctx->data()->SetLineCallback( asFUNCTION(ScriptEngine::LineCallback), &to, asCALL_CDECL );
		return ctx->execute();
	}
	ScriptModulePtr ScriptEngine::buildStreamAsModule( const std::string &name, StreamPtr stream, ScriptModuleFlags flags )
	{
		ScriptModulePtr module(createModule(name, flags));
		module->addSection( stream->getName(), stream->readAll() );
		module->build();
		return module;
			
	}
	
	bool ScriptEngine::runModule( const std::string &name, const std::string &entry_point, ScriptContextPtr context )
	{
		assert(mEngine);
		ScriptModulePtr ptr(findModule(name));
		return runModule(ptr, entry_point, context);
	}
	bool ScriptEngine::runModule( const ScriptModulePtr &module, const std::string &entry_point, ScriptContextPtr context )
	{
		assert(mEngine);
		if( !module ){
			return false; 
		}
		ScriptContextPtr ctx = context?context:nextFreeContext();
		int r = module->execute( entry_point, ctx );
		if( r != asEXECUTION_FINISHED )
		{
			// The execution didn't complete as expected. Determine what happened.
			if( r == asEXECUTION_EXCEPTION || r == asEXECUTION_ERROR )
			{
				// An exception occurred, let the script writer know what happened so it can be corrected.
				RuntimeError ex( EXC_RUNTIME_ERROR, std::string(ctx->data()->GetExceptionString()), std::string(ctx->data()->GetExceptionFunction()->GetName()), ctx->data()->GetExceptionLineNumber(), module->getName() );
				getCore()->error( ex.what() );
				throw ex;
			}
		}
		return true;
	}
	ScriptContextPtr ScriptEngine::nextFreeContext()
	{
		ScriptContextPtr p(findFreeContext());
		if(!p){
			p=new ScriptContext(mEngine->CreateContext(), this);
			mContexts.push_back( p );
		}
		else{
			p->create();
		}
		p->data()->ClearLineCallback();
		static DWORD timeOut;
		timeOut = timeGetTime()+mTimeOut;
		p->data()->SetLineCallback(asFUNCTION(LineCallback), &timeOut, asCALL_CDECL);
		return p;
	}
	ScriptContextPtr ScriptEngine::findFreeContext() const
	{
		for( auto i=mContexts.begin(); i!=mContexts.end(); i++ )
			if( i->get() && !i->get()->isCreated() ) return *i;
		return NULL;
	}
	ScriptModulePtr ScriptEngine::createModule( const std::string &name, ScriptModuleFlags flags ){
		assert( mEngine );
		ScriptModulePtr ctx( findModule( name ) );
		if( ctx && flags == SMF_ALWAYS_CREATE ) releaseModule(ctx);
		else if( flags == SMF_ONLY_IF_EXISTS )
		{
			if( !ctx ) throw ScriptEngineError( "Cannot find module '"+name+"', createModule specified with '"+trFlags(flags)+"'", EXC_LOC );
			return ctx;
		}
		std::string n = name;
		if(!ctx) 
			ctx = new ScriptModule(this, n, flags);
		mModules.push_back( ctx );
		return ctx;
	}
	ScriptModulePtr ScriptEngine::findModule( const std::string &name ){
		assert( mEngine );
		for( auto i=mModules.begin(); i!=mModules.end(); i++ )
			if( i->get() && i->get()->getName() == name ) return i->get();
		return NULL;
	}
	void ScriptEngine::releaseModule( ScriptModulePtr module ){
		assert( mEngine );
		if( !module ) return;
		for( auto i=mModules.begin(); i!=mModules.end(); i ++ )
		{
			if( (*i) == module )
			{
				mModules.erase( i );
				break;
			}
		}
		std::string name = module->getName();
		int r = mEngine->DiscardModule( name.c_str() );
		module.destroy();
	}
};